from numpy import *
from os import listdir
import operator

__author__ = 'zjw'

"""
    kNN算法-手写识别系统
"""


def img2vector(filename):
    """
    将文件中的图像（这里是一个数字二维矩阵）转化为以为的数组
    :param filename: 文件路径和文件名
    :return:
    """

    # 创建一个1行1024列的数组，每个值都为0
    returnVector = zeros((1, 1024))
    fr = open(filename)
    for i in range(32):  # 遍历行
        lineStr = fr.readline()  # 取出一整行的数据
        for j in range(32):  # 遍历列
            # 将一行的数据相接在returnVector数组中
            returnVector[0, 32 * i + j] = int(lineStr[j])
    return returnVector


def classify(inX, dataSet, labels, k):
    """
    分类器，通过距离计算公式，获取k个最近特征点，统计得出待判断特征值的类型。
    :param inX: 传入需要测试的列表
    :param dataSet: 特征集合
    :param labels: 类别集合
    :param k: 匹配次数
    :return: 返回训练结果，即所属类型
    """

    # 欧式距离公式：d = [(xA0 - xB0)^2 + (xA1 - xB1)^2]^0.5
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    # 获得排序后各个值在原数组中的索引
    sortedDistIndicies = distances.argsort()
    classCount = {}
    # 选择距离最小的k个点进行统计
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # 逆序排序，获得票数最多的特征值
    sortedClassCount = sorted(classCount.items(),
                              key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


def handwritingClassTest():
    """
    使用kNN算法中的分类器来测试识别手写数字系统。
    其中特征集数组放在trainingDigits文件夹下面，待测试特征集数据放在testDigits文件夹下面。
    :return:
    """

    # 依次存放各个图像所表示的数字
    hwLabels = []
    # listdir()函数是os模块下的函数，功能是可以列出指定文件夹下的文件名。
    trainingFileList = listdir('trainingDigits')
    # 计算trainingDigits文件夹下文件数量
    m = len(trainingFileList)
    # 生成一个m行1024列的数组，数组中各个值为0，用来存放m个1024个特征值。
    trainingMat = zeros((m, 1024))
    # 遍历所有训练图像
    for i in range(m):
        # 依次获取图像的txt文件名
        fileNameStr = trainingFileList[i]
        # 通过split()函数将文件名分成名字和txt两部分
        fileStr = fileNameStr.split('.')[0]
        # 再将前缀分成数字和序号两部分
        classNumStr = int(fileStr.split('_')[0])
        # 将图像表示的数字依次存入到hwLabels数组中
        hwLabels.append(classNumStr)
        # 通过路径和文件名传入到img2vector函数中，将相应txt中的图像转化为数组，并存入到trainingMat矩阵中的第i行
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
    # 初始化错误次数为0
    errorCount = 0.0
    # listdir函数列出了testDigits文件夹下的文件名
    testFileList = listdir('testDigits')
    # 计算testDigits文件下的文件数量
    mTest = len(testFileList)
    # 遍历所有测试图像
    for i in range(mTest):
        # 同上，先取出文件名，切割文件名取出文件中图像的数字。
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        # 通过路径和文件名传入img2vector函数中，将相应txt中的图像转化为数组，并存入到vectorUnderTest这个待测试特征矩阵中
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        # 将待测试特征矩阵，特征集矩阵，特征集对应的数字和k依次传入到分类器中进行运算测试，得到测试结果的数字
        classifierResult = classify(vectorUnderTest, trainingMat, hwLabels, 3)
        print("%d:分类器返回的数字为：%d，真正的数字是：%d" % (i+1, classifierResult, classNumStr))
        # 如果测试结果不等于真实的数字，则错误数量加1
        if classifierResult != classNumStr:
            errorCount += 1.0
    print("\n 分类器筛选出来错误的情况有：%d次" % errorCount)
    # 计算错误率
    print("\n 分类器的错误率为：%f" % (errorCount / float(mTest)))


if __name__ == '__main__':
    handwritingClassTest()
